"""Helpers for template integration."""

from collections.abc import Callable
from enum import StrEnum
import hashlib
import itertools
import logging
from typing import Any

import voluptuous as vol

from homeassistant.components import blueprint
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    ATTR_ENTITY_ID,
    CONF_ENTITY_PICTURE_TEMPLATE,
    CONF_FRIENDLY_NAME,
    CONF_ICON,
    CONF_ICON_TEMPLATE,
    CONF_NAME,
    CONF_PLATFORM,
    CONF_STATE,
    CONF_UNIQUE_ID,
    CONF_VALUE_TEMPLATE,
    SERVICE_RELOAD,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import issue_registry as ir, template
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import (
    AddConfigEntryEntitiesCallback,
    AddEntitiesCallback,
    async_get_platforms,
)
from homeassistant.helpers.issue_registry import IssueSeverity
from homeassistant.helpers.script_variables import ScriptVariables
from homeassistant.helpers.singleton import singleton
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import yaml as yaml_util
from homeassistant.util.hass_dict import HassKey

from .const import (
    CONF_ADVANCED_OPTIONS,
    CONF_ATTRIBUTE_TEMPLATES,
    CONF_ATTRIBUTES,
    CONF_AVAILABILITY,
    CONF_AVAILABILITY_TEMPLATE,
    CONF_DEFAULT_ENTITY_ID,
    CONF_PICTURE,
    DOMAIN,
    PLATFORMS,
)
from .entity import AbstractTemplateEntity
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity

LEGACY_TEMPLATE_DEPRECATION_KEY = "deprecate_legacy_templates"

DATA_BLUEPRINTS = "template_blueprints"
DATA_DEPRECATION: HassKey[list[str]] = HassKey(LEGACY_TEMPLATE_DEPRECATION_KEY)

LEGACY_FIELDS = {
    CONF_ICON_TEMPLATE: CONF_ICON,
    CONF_ENTITY_PICTURE_TEMPLATE: CONF_PICTURE,
    CONF_AVAILABILITY_TEMPLATE: CONF_AVAILABILITY,
    CONF_ATTRIBUTE_TEMPLATES: CONF_ATTRIBUTES,
    CONF_FRIENDLY_NAME: CONF_NAME,
}

_LOGGER = logging.getLogger(__name__)

type CreateTemplateEntitiesCallback = Callable[
    [type[TemplateEntity], AddEntitiesCallback, HomeAssistant, list[dict], str | None],
    None,
]


@callback
def templates_with_blueprint(hass: HomeAssistant, blueprint_path: str) -> list[str]:
    """Return all template entity ids that reference the blueprint."""
    return [
        entity_id
        for platform in async_get_platforms(hass, DOMAIN)
        for entity_id, template_entity in platform.entities.items()
        if isinstance(template_entity, AbstractTemplateEntity)
        and template_entity.referenced_blueprint == blueprint_path
    ]


@callback
def blueprint_in_template(hass: HomeAssistant, entity_id: str) -> str | None:
    """Return the blueprint the template entity is based on or None."""
    for platform in async_get_platforms(hass, DOMAIN):
        if isinstance(
            (template_entity := platform.entities.get(entity_id)),
            AbstractTemplateEntity,
        ):
            return template_entity.referenced_blueprint
    return None


def _blueprint_in_use(hass: HomeAssistant, blueprint_path: str) -> bool:
    """Return True if any template references the blueprint."""
    return len(templates_with_blueprint(hass, blueprint_path)) > 0


async def _reload_blueprint_templates(hass: HomeAssistant, blueprint_path: str) -> None:
    """Reload all templates that rely on a specific blueprint."""
    await hass.services.async_call(DOMAIN, SERVICE_RELOAD)


@singleton(DATA_BLUEPRINTS)
@callback
def async_get_blueprints(hass: HomeAssistant) -> blueprint.DomainBlueprints:
    """Get template blueprints."""
    from .config import TEMPLATE_BLUEPRINT_SCHEMA  # noqa: PLC0415

    return blueprint.DomainBlueprints(
        hass,
        DOMAIN,
        _LOGGER,
        _blueprint_in_use,
        _reload_blueprint_templates,
        TEMPLATE_BLUEPRINT_SCHEMA,
    )


def rewrite_legacy_to_modern_config(
    hass: HomeAssistant,
    entity_cfg: dict[str, Any],
    extra_legacy_fields: dict[str, str],
) -> dict[str, Any]:
    """Rewrite legacy config."""
    entity_cfg = {**entity_cfg}

    # Remove deprecated entity_id field from legacy syntax
    entity_cfg.pop(ATTR_ENTITY_ID, None)

    for from_key, to_key in itertools.chain(
        LEGACY_FIELDS.items(), extra_legacy_fields.items()
    ):
        if from_key not in entity_cfg or to_key in entity_cfg:
            continue

        val = entity_cfg.pop(from_key)
        if isinstance(val, str):
            val = template.Template(val, hass)
        entity_cfg[to_key] = val

    if CONF_NAME in entity_cfg and isinstance(entity_cfg[CONF_NAME], str):
        entity_cfg[CONF_NAME] = template.Template(entity_cfg[CONF_NAME], hass)

    return entity_cfg


def rewrite_legacy_to_modern_configs(
    hass: HomeAssistant,
    domain: str,
    entity_cfg: dict[str, dict],
    extra_legacy_fields: dict[str, str],
) -> list[dict]:
    """Rewrite legacy configuration definitions to modern ones."""
    entities = []
    for object_id, entity_conf in entity_cfg.items():
        entity_conf = {**entity_conf, CONF_DEFAULT_ENTITY_ID: f"{domain}.{object_id}"}

        entity_conf = rewrite_legacy_to_modern_config(
            hass, entity_conf, extra_legacy_fields
        )

        if CONF_NAME not in entity_conf:
            entity_conf[CONF_NAME] = template.Template(object_id, hass)

        entities.append(entity_conf)

    return entities


@callback
def async_create_template_tracking_entities(
    entity_cls: type[Entity],
    async_add_entities: AddEntitiesCallback,
    hass: HomeAssistant,
    definitions: list[dict],
    unique_id_prefix: str | None,
) -> None:
    """Create the template tracking entities."""
    entities: list[Entity] = []
    for definition in definitions:
        unique_id = definition.get(CONF_UNIQUE_ID)
        if unique_id and unique_id_prefix:
            unique_id = f"{unique_id_prefix}-{unique_id}"
        entities.append(entity_cls(hass, definition, unique_id))  # type: ignore[call-arg]
    async_add_entities(entities)


def _format_template(value: Any, field: str | None = None) -> Any:
    if isinstance(value, template.Template):
        return value.template

    if isinstance(value, StrEnum):
        return value.value

    if isinstance(value, (int, float, str, bool)):
        return value

    return str(value)


def format_migration_config(
    config: ConfigType | list[ConfigType], depth: int = 0
) -> ConfigType | list[ConfigType]:
    """Recursive method to format templates as strings from ConfigType."""
    if depth > 9:
        raise RecursionError

    if isinstance(config, list):
        items = []
        for item in config:
            if isinstance(item, (dict, list)):
                if len(item) > 0:
                    items.append(format_migration_config(item, depth + 1))
            else:
                items.append(_format_template(item))
        return items  # type: ignore[return-value]

    formatted_config = {}
    for field, value in config.items():
        if isinstance(value, dict):
            if len(value) > 0:
                formatted_config[field] = format_migration_config(value, depth + 1)
        elif isinstance(value, list):
            if len(value) > 0:
                formatted_config[field] = format_migration_config(value, depth + 1)
            else:
                formatted_config[field] = []
        elif isinstance(value, ScriptVariables):
            formatted_config[field] = format_migration_config(
                value.as_dict(), depth + 1
            )
        else:
            formatted_config[field] = _format_template(value)

    return formatted_config


def create_legacy_template_issue(
    hass: HomeAssistant, config: ConfigType, domain: str
) -> None:
    """Create a repair for legacy template entities."""
    if domain not in PLATFORMS:
        return

    breadcrumb = "Template Entity"
    # Default entity id should be in most legacy configuration because
    # it's created from the legacy slug. Vacuum and Lock do not have a
    # slug, therefore we need to use the name or unique_id.
    if (default_entity_id := config.get(CONF_DEFAULT_ENTITY_ID)) is not None:
        breadcrumb = default_entity_id.split(".")[-1]
    elif (unique_id := config.get(CONF_UNIQUE_ID)) is not None:
        breadcrumb = f"unique_id: {unique_id}"
    elif (name := config.get(CONF_NAME)) and isinstance(name, template.Template):
        breadcrumb = name.template

    issue_id = f"{LEGACY_TEMPLATE_DEPRECATION_KEY}_{domain}_{breadcrumb}_{hashlib.md5(','.join(config.keys()).encode()).hexdigest()}"

    if (deprecation_list := hass.data.get(DATA_DEPRECATION)) is None:
        hass.data[DATA_DEPRECATION] = deprecation_list = []

    deprecation_list.append(issue_id)

    try:
        config.pop(CONF_PLATFORM, None)
        modified_yaml = format_migration_config(config)
        yaml_config = (
            f"```\n{yaml_util.dump({DOMAIN: [{domain: [modified_yaml]}]})}\n```"
        )
    except RecursionError:
        yaml_config = f"{DOMAIN}:\n  - {domain}:      - ..."

    ir.async_create_issue(
        hass,
        DOMAIN,
        issue_id,
        breaks_in_ha_version="2026.6",
        is_fixable=False,
        severity=IssueSeverity.WARNING,
        translation_key="deprecated_legacy_templates",
        translation_placeholders={
            "domain": domain,
            "breadcrumb": breadcrumb,
            "config": yaml_config,
            "filename": "<filename>",
        },
    )


async def async_setup_template_platform(
    hass: HomeAssistant,
    domain: str,
    config: ConfigType,
    state_entity_cls: type[TemplateEntity],
    trigger_entity_cls: type[TriggerEntity] | None,
    async_add_entities: AddEntitiesCallback,
    discovery_info: DiscoveryInfoType | None,
    legacy_fields: dict[str, str] | None = None,
    legacy_key: str | None = None,
) -> None:
    """Set up the Template platform."""
    if discovery_info is None:
        # Legacy Configuration
        if legacy_fields is not None:
            if legacy_key:
                configs = rewrite_legacy_to_modern_configs(
                    hass, domain, config[legacy_key], legacy_fields
                )
            else:
                configs = [rewrite_legacy_to_modern_config(hass, config, legacy_fields)]

            for definition in configs:
                create_legacy_template_issue(hass, definition, domain)

            async_create_template_tracking_entities(
                state_entity_cls,
                async_add_entities,
                hass,
                configs,
                None,
            )
        else:
            _LOGGER.warning(
                "Template %s entities can only be configured under template:", domain
            )
        return

    # Trigger Configuration
    if "coordinator" in discovery_info:
        if trigger_entity_cls:
            entities = [
                trigger_entity_cls(hass, discovery_info["coordinator"], config)
                for config in discovery_info["entities"]
            ]
            async_add_entities(entities)
        else:
            raise PlatformNotReady(
                f"The template {domain} platform doesn't support trigger entities"
            )
        return

    # Modern Configuration
    async_create_template_tracking_entities(
        state_entity_cls,
        async_add_entities,
        hass,
        discovery_info["entities"],
        discovery_info["unique_id"],
    )


async def async_setup_template_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
    state_entity_cls: type[TemplateEntity],
    config_schema: vol.Schema | vol.All,
    replace_value_template: bool = False,
) -> None:
    """Setup the Template from a config entry."""
    options = dict(config_entry.options)
    options.pop("template_type")

    if advanced_options := options.pop(CONF_ADVANCED_OPTIONS, None):
        options = {**options, **advanced_options}

    if replace_value_template and CONF_VALUE_TEMPLATE in options:
        options[CONF_STATE] = options.pop(CONF_VALUE_TEMPLATE)

    validated_config = config_schema(options)

    async_add_entities(
        [state_entity_cls(hass, validated_config, config_entry.entry_id)]
    )


def async_setup_template_preview[T: TemplateEntity](
    hass: HomeAssistant,
    name: str,
    config: ConfigType,
    state_entity_cls: type[T],
    schema: vol.Schema | vol.All,
    replace_value_template: bool = False,
) -> T:
    """Setup the Template preview."""
    if replace_value_template and CONF_VALUE_TEMPLATE in config:
        config[CONF_STATE] = config.pop(CONF_VALUE_TEMPLATE)

    validated_config = schema(config | {CONF_NAME: name})
    return state_entity_cls(hass, validated_config, None)
